// Server.cpp
// (c) 2008, Charles Lechasseur
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "Server.h"


namespace RRO {
namespace COM
{
// -- InvalidObjectCountException class --

    //
    // Constructor.
    //
    InvalidObjectCountException::InvalidObjectCountException(void)
        : Exception()
    {
    }

    //
    // Copy constructor.
    //
    InvalidObjectCountException::InvalidObjectCountException(const InvalidObjectCountException& p_Exception)
        : Exception(p_Exception)
    {
    }

    //
    // Assignment operator.
    //
    InvalidObjectCountException& InvalidObjectCountException::operator=(
        const InvalidObjectCountException& p_Exception)
    {
        if (this != &p_Exception) {
            Exception::operator=(p_Exception);
        }
        return *this;
    }

    //
    // Destructor.
    //
    InvalidObjectCountException::~InvalidObjectCountException(void)
    {
    }

    //
    // Returns exception's description.
    //
    const char* InvalidObjectCountException::what(void) const
    {
        return "RRO::COM::InvalidObjectCountException";
    }

// -- Server class --

    //
    // Static lock for protecting the creation of the COM server singleton.
    //
    CriticalSection         Server::s_ServerLock;

    //
    // Static auto-pointer containing the reference to the COM server singleton.
    // The content of the pointer will be initialized on the first access to Server::Instance().
    //
    std::auto_ptr<Server>   Server::s_Server;

    //
    // Constructor. Initializes object count to 0.
    //
    Server::Server(void)
        : m_ObjectCount(0),
          m_Factories()
    {
    }

    //
    // Destructor.
    //
    Server::~Server(void)
    {
    }

    //
    // Returns the global instance of the COM server. The server singleton is initialized on the first call
    // in a thread-safe manner.
    //
    Server& Server::Instance()
    {
        // Check to see if instance exists. This saves locks for most calls.
        if (s_Server.get() == 0) {
            // Have to create instance, get the lock.
            CriticalSection::Lock lock(s_ServerLock);

            // Check again, since another thread might have ended up here before we did.
            if (s_Server.get() == 0) {
                // Still need to create the instance.
                s_Server.reset(new Server());
            }
        }

        // Return server instance.
        return *s_Server;
    }

    //
    // Adds an object reference to the server. This is called each time a COM object is created and
    // allows the server to keep track of live object instances.
    //
    void Server::AddObject(void)
    {
        ::InterlockedIncrement(reinterpret_cast<LONG*>(&m_ObjectCount));
    }

    //
    // Removes an object reference from the server. This is called each time a COM object is released.
    //
    void Server::RemoveObject(void)
    {
        long result = ::InterlockedDecrement(reinterpret_cast<LONG*>(&m_ObjectCount));
        // We're saving result as long to be able to trap underflow conditions.
        if (result < 0) {
            // We actually need to undo our decrement here since otherwise server would be screwed.
            // Note that this is not thread-safe, but should be OK if we don't screw up the count. :)
            AddObject();
            throw InvalidObjectCountException();
        }
    }

    //
    // Returns the number of object instances currently registered by the server.
    //
    unsigned long Server::ObjectCount(void) const
    {
        return m_ObjectCount;
    }

    //
    // Checks if the server can close. The server cannot close if any COM objects are still active.
    //
    bool Server::CanClose(void) const
    {
        return ObjectCount() > 0;
    }

    //
    // Registers a class factory in the COM server. This will allow the COM server to provide
    // access to the class factory later to create COM objects.
    //
    // NOTE: this method does not use locks, so registering factories is not thread-safe. Please
    // register all factories before creating threads to access them.
    //
    // @param p_pFactory Factory to register.
    //
    void Server::RegisterFactory(ClassFactory* p_pFactory)
    {
        if (p_pFactory != 0) {
            m_Factories.insert(std::make_pair(p_pFactory->ClassID(), AutoPtr<ClassFactory>(p_pFactory)));
        }
    }

    //
    // Finds a class factory supporting the given class ID among the registered factories.
    //
    // @param p_ClassID ID of the class that needs to be created.
    // @returns Factory supporting that class. Can be null if no factories support that class.
    //
    ClassFactory* Server::FindFactory(const CLSID& p_ClassID) const
    {
        ClassFactory* pFactory = 0;
        ClassFactoryMap::const_iterator factoryIter = m_Factories.find(p_ClassID);
        if (factoryIter != m_Factories.end()) {
            pFactory = factoryIter->second;
        }
        return pFactory;
    }

    //
    // Registers the COM server and all its factories in the registry.
    //
    void Server::RegisterServer(void)
    {
        // Iterate factories and ask them to update the registry.
        for (ClassFactoryMap::iterator it = m_Factories.begin();
            it != m_Factories.end(); ++it)
        {
            try {
                it->second->UpdateRegistry(true);
            } catch (...) {
                // We want to let other factories register, so silence the exception here.
            }
        }
    }

    //
    // Unregisters the COM server and all its factories from the registry.
    //
    void Server::UnregisterServer(void)
    {
        // Iterate factories and ask them to update the registry.
        for (ClassFactoryMap::iterator it = m_Factories.begin();
            it != m_Factories.end(); ++it)
        {
            try {
                it->second->UpdateRegistry(false);
            } catch (...) {
                // We want to let other factories unregister, so silence the exception here.
            }
        }
    }

}; // namespace COM
}; // namespace RRO

//
// Simple CLSID comparator. Used to create a map of class factories sorted by CLSID.
//
bool operator<(const CLSID& p_Left, const CLSID& p_Right)
{
    return ::memcmp(
        static_cast<void*>(const_cast<CLSID*>(&p_Left)),
        static_cast<void*>(const_cast<CLSID*>(&p_Right)),
        sizeof(CLSID)) < 0;
}

//
// Registers the in-process COM server in the registry.
//
STDAPI DllRegisterServer(void)
{
    RRO::COM::Server::Instance().RegisterServer();
    return S_OK;
}

//
// Unregisters the in-process COM server by removing registry entries.
//
STDAPI DllUnregisterServer(void)
{
    RRO::COM::Server::Instance().UnregisterServer();
    return S_OK;
}

//
// Returns the class factory that can create the given class.
//
// @param p_ClassID ID of the class for which a factory is needed.
// @param p_IID ID of interface requested from the factory object (usually IID_IClassFactory).
// @param p_ppObject Pointer to variable where to store factory reference.
//
STDAPI DllGetClassObject(REFCLSID p_ClassID, REFIID p_IID, void** p_ppObject)
{
    HRESULT res = E_UNEXPECTED;

    if (p_ppObject == 0) {
        res = E_INVALIDARG;
    } else {
        // Ask server to find the factory.
        RRO::COM::AutoPtr<RRO::COM::ClassFactory> factory(RRO::COM::Server::Instance().FindFactory(p_ClassID));

        // Check if factory was available.
        if (!factory.IsNull()) {
            // Query the factory for the requested IID and return the result.
            res = factory->QueryInterface(p_IID, p_ppObject);
        } else {
            // No factory for this class ID.
            *p_ppObject = 0;
            res = CLASS_E_CLASSNOTAVAILABLE;
        }
    }

    return res;
}

//
// Verifies if the in-process COM server can be unloaded, to know if there are still
// active objects. This is implemented by checking the COM server's object count.
//
STDAPI DllCanUnloadNow(void)
{
    return (RRO::COM::Server::Instance().CanClose() ? S_OK : S_FALSE);
}
